<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <script>
    var data = [
      // { id: 1, name: "办公管理", pid: 0 }, 根节点如果放在最后一个出现，第二种方法就会报错
      { id: 2, name: "请假申请", pid: 1 },
      { id: 3, name: "出差申请", pid: 1 },
      { id: 4, name: "请假记录", pid: 2 },
      { id: 5, name: "系统设置", pid: 0 },
      { id: 6, name: "权限管理", pid: 5 },
      { id: 7, name: "用户角色", pid: 6 },
      { id: 8, name: "菜单设置", pid: 6 },
      { id: 1, name: "办公管理", pid: 0 },
    ];
    // 观察数据结构 pid 指的就是parent id ,就是它的父级的id
    // 这样一看就好理解的多了
    function toTree (data) {
      // 删除 所有 children,以防止多次调用
      // forEach 没有返回值，原数组不改变
      data.forEach(function (item) {
        delete item.children;
      });
      //*******************************************************************************

      // 将数据存储为 以 id 为 KEY 的 map 索引数据列
      var maps = {};
      data.forEach(function (item) {
        maps[item.id] = item;
      });
      // console.log(maps);
      // 打印结果如下
      // 1：{id: 1, name: '办公管理', pid: 0}
      // 2：{id: 2, name: '请假申请', pid: 1}
      // 3：{id: 3, name: '出差申请', pid: 1}
      // 4：{id: 4, name: '请假记录', pid: 2}
      // 5：{id: 5, name: '系统设置', pid: 0}
      // 6：{id: 6, name: '权限管理', pid: 5}
      // 7：{id: 7, name: '用户角色', pid: 6}
      // 8：{id: 8, name: '菜单设置', pid: 6}

      //这一步操作的结果就是maps成为了一个对象的集合,内部是对象套对象
      // 的形式,原来是数组套对象,现在直接是对象套对象
      // 对象的key值是1,2 3.......
      // value值就是item
      // 也可以直接深复制一份
      // console.log(JSON.parse(JSON.stringify(data)));
      // 不行,达不到上面那个效果,这个的外层还是一个数组
      // ******************************************************************************************************
      var val = [];
      // 注意这里遍历的是原始的数据集,就还是那个数组形式的data
      //那么我需要 当前项item的pid值 也就是 item.pid 就是0 1 2 5 6 这几个数
      // 然后这个值拿到之后在之前我们生成的map中去索引 分析
      // 1.当pid = 0 时, 就是父亲的id是0的这个item,maps中没有这个索引,因为它是从1开始的
      // 2. 所以它需要在val中直接把这个pid = 0的item给推进去,他就是最顶级的实例
      //   就是先打底 { id: 1, name: "办公管理", pid: 0 },是一个顶级实例，存到val
      // 3. 继续第二步,就是当pid=1时, 就是父级的id是1,
      //    这里的意思就是第二行数据它的父亲的id是1 maps.1是有值的
      // 4. 这时,parent就不是空值,它是存在的，就是1：{id: 1, name: '办公管理', pid: 0}
      // 5. 当前的值 是{ id: 2, name: "请假申请", pid: 1 },它的父级是1：{id: 1, name: '办公管理', pid: 0}
      // 6. 会给parent当子级,放在children中，这个数据是会存放在内存中，不会释放，没有把它放到其他数据中
      // 7.第三次 pid等于1, 就是父级的id是1,还有,就还放在children中
      // 8. 第四次pid = 2,就是父级的id是2 存在,就给id为2的item当子级,放在一级菜单的后面
      //     这里每一个的id值是唯一的,但是它的父级id是可以自己指定的
      // 9.第五次的时候,又没有了,就在次起了一个序列开始,然后给他添加子级
      // 10.在pid = 5 时,把它加在0后面 等于6时,加在5后面
      // 即 var parent = map[item.pid] ,这个parent 便是 item的父级对象
      data.forEach(function (item) {
        // console.log(data, item);
        // 以当前遍历项的pid,去maps对象中找到索引的id
        // 这里先做第一个赋值,就是把maps对象中的item.pid这个值拿出来当做父级
        // 如果之前父级不存在,就直接把这个item放到父级的位置
        // 如果找到,那说明它不是顶级,把它放到对应的父级中去
        /*2000/4/3
        这里使用的是引用类型的复制，所以是一个浅拷贝*/
        var parent = maps[item.pid];
        // console.log('打印一下这个参数' ,item.pid);
        // console.log('打印一下这个全部参数' ,maps[item.pid]);
        // 好绕啊，如果找到索引，那么说明此项不在顶级当中,那么需要把此项添加到，他对应的父级中
        // 这里最难得就是给他找对应的父级，这里它的父级就是parent,是一个临时存储的变量，循环结束就消失
        // 但是在它消失之前，它修改了自己的值，给自己加了children,因为它是浅克隆的maps，而maps又是
        // 浅克隆的data，所以只要parent一修改，对应的maps和data都会跟着修改
        if (parent) {
          // 这里是逻辑或的判断,父级的子级没有东西就直接放,如果有,就往后面加,不能覆盖原有的数据
          // parent.children.push(item);
          // 本来直接写这个就可以，但是需要给parent加一个属性就是children，
          // 因为是新加，就必须要确定children的类型，如果直接写parent.children = []
          // 就会每次都把这个数据给清空，重置，不符合需求，所以就需要进行一下判断，如果有children属性
          // 就往上面加，如果没有，就新建一个空的给他
          // if (parent.children) {
          //   parent.children.push(item)
          // } else {
          //   parent.children = []
          //   parent.children.push(item)
          // }
          // 这个代码是原有的if判断的简化版本
          /*2000/4/3
          这里使用的是引用类型的复制，所以是一个浅拷贝
          所以在这里改变parent值的时候，maps的值也被改动了，这时的maps
          打印之后就会发现其实是一个被修改过的对象嵌套的形式
          在单词的循环中不会出什么问题，因为所有的子级对象只被添加了一次
          如果调用两次，就会出现数据的重叠，因为children被添加了多次
          所以方法开头的判断就很有效果，删除 所有 children,以防止多次调用产生脏数据*/
          // 这里需要强调修改parent的时候，其实maps已经被修改了，data的值也跟着变化了,这是因为这里使用的
          // 都是浅复制，一个修改，所有的都会受影响，所以data的原始数组也改变了，这里只要循环data，然后把data
          // 中符合要求的选项放入目标驻足，
          (parent.children || (parent.children = [])).push(item);
        } else {
          //如果没有在map中找到对应的索引ID,那么直接把 当前的item添加到 val结果集中，作为顶级
          val.push(item);
        }
      });
      return val;
    }
    toTree(data)
    console.log('111', toTree(data))
    // console.log(data);

    // 对上一个方法进行优化
    // 优化会出现一些个问题，如果根节点出现在最后一个，那么他就会因为在每次循环的时候找不到根节点
    // 就会误以为当前节点就是一个根节点，然后把它放进list中，导致list中的数据报错
    function toTree2 (data) {
      const maps = {};
      data.forEach(item => {
        maps[item.id] = { ...item }
      })
      const treeData = [];
      data.forEach((item) => {
        const { id, pid } = item;
        // maps[id] = { ...item };
        const parent = maps[pid];
        if (parent) {
          (parent.children || (parent.children = [])).push(maps[id]);
          // 这样就可以观察每次对父节点的处理，怎么给他加子节点
          // let parent1 = JSON.parse(JSON.stringify(parent))
          // console.log('parent', parent1);
        } else {
          treeData.push(maps[id]);
          // 这样可以观察对根节点的处理
          // let list1 = JSON.parse(JSON.stringify(treeData))
          // console.log('list', list1);
        }
        // 这行代码基本是可以省略的，在这个情形中
        delete maps[id].children;
      });
      return treeData;
    }
    console.log('toTree2', toTree2(data));

    // 真正采用递归的方式来实现
    function buildTree (data, pid) {
      pid = pid || 0
      const tree = []
      data.map(item => {
        if (item.pid === pid) {
          const children = buildTree(data, item.id)
          // 如果没有子级，就不给空数组了
          if (children.length > 0) {
            item.children = children
          }
          tree.push(item)
        }
      })
      return tree
    }
    const tree = buildTree(data, 0)
    console.log('buildTree', tree)
  </script>
</body>

</html>